/*
# pymoz, Python extension for the Mozilla Toolkit
# Copyright (C) 2012 Malek Hadj-Ali
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#ifndef pyRuntime_h_
#define pyRuntime_h_


#include "pymoz.h"

#include "pyIRuntime.h"
#include "nsIObserver.h"
#include "mozilla/ModuleLoader.h"

#include "nsIXPConnect.h"
#include "nsIJSRuntimeService.h"
#include "nsIJSContextStack.h"
#include "xpcIJSGetFactory.h"

#include "nsDataHashtable.h"
#include "nsClassHashtable.h"
#include "nsNetUtil.h"


#define PY_RUNTIME_CID \
    { 0xa1c8e2d4, 0xbcb1, 0x476a, \
      { 0x87, 0x60, 0x4c, 0xd4, 0x2c, 0x79, 0x5a, 0xce } }

#define PY_RUNTIME_CONTRACTID "@pymoz.com/python/runtime;1"


class JSAutoContextPusher
{
public:
    JSAutoContextPusher(JSContext *cx);
    ~JSAutoContextPusher();
};


class JSHolder
{
public:
    JSHolder(JSObject *jsobj);
    ~JSHolder();
    bool Hold();
    JSObject *GetPrivate();

private:
    JSObject *mJSObj;
};


class pyModule : public mozilla::Module
{
public:
    pyModule() : mozilla::Module() {
        mVersion = mozilla::Module::kVersion;
        mCIDs = NULL;
        mContractIDs = NULL;
        mCategoryEntries = NULL;
        getFactoryProc = GetFactory;
        loadProc = NULL;
        unloadProc = NULL;

        mModule = NULL;
    }

    ~pyModule() {
        Clear();
    }

    void Clear() {
        getfactoryobj = NULL;
        Py_CLEAR(mModule);
    }

    static already_AddRefed<nsIFactory>
        GetFactory(const mozilla::Module& module,
                   const mozilla::Module::CIDEntry& entry);

    bool Init(nsIFile *aFile, const char *aPath);

private:
    PyObject *ImportModuleFromFile(FILE *fp, const char *path, const char *name);

    PyObject *mModule;
    nsCOMPtr<xpcIJSGetFactory> getfactoryobj;
};


typedef nsDataHashtable<nsCStringHashKey, pyModule *> ModuleCache;
typedef nsDataHashtable<nsPtrHashKey<JSObject>, PyObject *> PyObjectCache;
typedef nsClassHashtable<nsPtrHashKey<PyObject>, JSHolder> JSObjectCache;


class pyRuntime MOZ_FINAL : public pyIRuntime,
                            public nsIObserver,
                            public mozilla::ModuleLoader
{
public:
    NS_DECL_ISUPPORTS
    NS_DECL_PYIRUNTIME
    NS_DECL_NSIOBSERVER

    // mozilla::ModuleLoader
    const mozilla::Module *LoadModule(mozilla::FileLocation& aFileLocation);

    static pyRuntime *GetService();

    bool ConvertURL(const char *aSpec, nsACString& aPath);
    void ReportError();
    JSContext *GetJSContext();
    bool SetException(PyObject *aExcValue);
    bool ReportWarning(PyObject *aMessage, PyObject *aCategory,
                       PyObject *aFilename, PyObject *aLineno,
                       PyObject *aLine);

    bool HoldJSObject(JSObject **jsobj);
    void DropJSObject(JSObject **jsobj);
    bool GetObject(JSObject *jsobj, PyObject **pyobj);
    void PutObject(JSObject *jsobj, PyObject *pyobj);
    bool GetObject(PyObject *pyobj, JSObject **jsobj);
    void PutObject(PyObject *pyobj, JSObject *jsobj);

    PyObject *JSImportModule(const char *uri, PyObject *target);
    PyObject *JSGetGlobal();
    PyObject *JSInstanceOf(PyObject *obj, PyObject *proto);


    static pyRuntime *sPyRuntime;

    PyObject *mError;
    PyObject *mJSError;
    PyObject *mXPCOMError;
    PyObject *mJSWarning;
    JSObject *mJSPyTypeProto;
    PyObject *mXPCOMUtils;
    PyObject *mComponents;

private:
    pyRuntime();
    ~pyRuntime();

    bool PreSetup();
    bool SetupPython();
    bool SetupRuntime();
    bool SetupWrappers();
    bool SetupPymoz();
    bool SetupObserver();

    bool Setup();
    void Clear();

    bool ConvertFileURL(nsIFileURL *aFileURL, nsACString& aPath);
    bool ConvertChromeURL(nsIURI *aChromeURL, nsACString& aPath);
    already_AddRefed<nsIFile>
        GetFile(const nsACString& aSpec, const char *aOriginCharset,
                nsIURI *aBaseURI, nsACString& aPath);
    bool UpdateJSGlobal();


    nsresult mResult;

    nsCOMPtr<nsIXPConnect> mXPConnect;

    nsCOMPtr<nsIExceptionService> mExceptionService;
    nsCOMPtr<nsIExceptionManager> mExceptionManager;

    nsCOMPtr<nsIThreadJSContextStack> mJSContextStack;

    nsCOMPtr<nsIJSRuntimeService> mJSRuntimeService;
    JSRuntime *mJSRuntime;

    ModuleCache mModules;
    PyObjectCache mPyObjects;
    JSObjectCache mJSObjects;

    friend class JSAutoContextPusher;

    friend class pyModule;
};


#endif // pyRuntime_h_
